BAirboss = {
    carrierUnit = nil,

    Morse = nil,

    ATC = nil,

    TACAN = nil,
    TACAN_Mode = nil,
    TACAN_ON = false,

    ICLS = nil,
    ICLS_ON = false,

    LINK4 = nil,
    LINK4_ON = false,

    ACLS_ON = false,
}

do

--- Tables.
    BAirboss.AircraftTypes={
        AV8B="AV8BNA",
        HORNET="FA-18C_hornet",
        A4EC="A-4E-C",
        F14A="F-14A-135-GR",
        F14B="F-14B",
        F14A_AI="F-14A",
        FA18C="F/A-18C",
        T45C="T-45",
        S3B="S-3B",
        S3BTANKER="S-3B Tanker",
        E2D="E-2C",
        C2A="C2A_Greyhound",
        RHINOE="FA-18E",
        RHINOF="FA-18F",
        GROWLER="EA-18G",
    }

    BAirboss.CarrierType = {
        ROOSEVELT = "CVN_71",
        LINCOLN = "CVN_72",
        WASHINGTON = "CVN_73",
        TRUMAN = "CVN_75",
        STENNIS = "Stennis",
        FORRESTAL = "Forrestal",
        VINSON = "VINSON",
        HERMES = "HERMES81",
        INVINCIBLE = "hms_invincible",
        TARAWA = "LHA_Tarawa",
        AMERICA = "USS America LHA-6",
        JCARLOS = "L61",
        CANBERRA = "L02",
        KUZNETSOV = "KUZNECOW",
    }

    BAirboss.DefaultSettings = {
        [BAirboss.CarrierType.FORRESTAL] = {
            Morse = 'FRS',

            -- ATC = RadioManager.RegisterChannel(269000000,RadioManager.RadioTypes.Carrier,'CVN 59'),
            ATC = 259,
            
            MarshalRadio = nil,
            LSORadio = nil,

            TACAN_ON = false,
            TACAN = 59,
            TACAN_Mode = 'X',

            ICLS_ON = false,
            ICLS = 19,

            LINK4 = 339000000,
            LINK4_ON = false,

            ACLS_ON = false,
        },
         
        [BAirboss.CarrierType.ROOSEVELT] = {
            Morse = 'RSV',

            -- ATC = RadioManager.RegisterChannel(261000000,RadioManager.RadioTypes.Carrier,'CVN 71'),
            ATC = 261,

            MarshalRadio = nil,
            LSORadio = nil,

            TACAN_ON = false,
            TACAN = 71,
            TACAN_Mode = 'X',

            ICLS_ON = false,
            ICLS = 11,

            LINK4 = 331000000,
            LINK4_ON = false,

            ACLS_ON = false,
        },
         
        [BAirboss.CarrierType.STENNIS] = {
            Morse = 'STN',

            -- ATC = RadioManager.RegisterChannel(264000000,RadioManager.RadioTypes.Carrier,'CVN 74'),
            ATC = 264,

            MarshalRadio = nil,
            LSORadio = nil,

            TACAN_ON = false,
            TACAN = 74,
            TACAN_Mode = 'X',

            ICLS_ON = false,
            ICLS = 14,

            LINK4 = 334000000,
            LINK4_ON = false,

            ACLS_ON = false,
        },
         
    }
    BAirboss.AllCarriers = {}

--- Private functions
    -- event handler.
    local eventHandler = {}
    function eventHandler:onEvent(event)
        if event.id == world.event.S_EVENT_SIMULATION_UNFREEZE then
            BAirboss.restartBeacons()
        end
    end
    world.addEventHandler(eventHandler) -- enable handler.

--- Public functions.
    function BAirboss:New(carrierUnitName)
        if not carrierUnitName then return end

        if type(carrierUnitName) ~= 'string' then 
            --errMsg

            return
        end
        
        -- local obj = Utils.deepCopy(self)
        local obj = {}

        obj.carrierUnit = Unit.getByName(carrierUnitName)

        if not obj.carrierUnit then 
            local errMsg = 'ERROR: Carrier unit %s is not found in mission.'
            errMsg = string.format(Localizations:translate(errMsg,'cn'),carrierUnitName)

            trigger.action.outText(errMsg,120)
            env.error(errMsg)
            return nil
        end

        setmetatable(obj,self)
        self.__index = self

        -- timer.scheduleFunction(function (vars, time)
        --     local carrier = vars.context
        --     carrier:InitBeacons()

        --     return time+1800
        -- end,{context = obj},timer.getTime()+5)

        env.info('BAirboss - New Carrier: '..carrierUnitName)
        BAirboss.AllCarriers[carrierUnitName] = obj
        return obj
    end

    function BAirboss.findClearPath(from,heading,range)
        local dest = Utils.vecTranslate(from,heading,range)

        range = range or Utils.get2DDist(from,dest)
        heading = heading or Utils.getDirection(from,dest)
        
        local iterator = 40
        local partLength = range/iterator

        for i=1,iterator,1 do
            local testPoint = Utils.vecTranslate(from,heading,partLength*i)
            local lastPoint = from
            if i>1 then
                lastPoint = Utils.vecTranslate(from,heading,partLength*(i-1))
            end

            if i>10 then
                lastPoint = Utils.vecTranslate(from,heading,partLength*(i-10))
            end

            if land.getSurfaceType(testPoint) ~= land.SurfaceType.WATER or land.getSurfaceType(testPoint) == land.SurfaceType.SHALLOW_WATER then
                dest = lastPoint
                break
            end
        end

        return dest
    end 

    -- @param DCS.Unit
    -- @return number ex '13' for '013'
    function BAirboss.getAirCraftOnBoardNumber(unit)
        if not unit then return end
        if unit:getLife()<1 or unit:isActive() then return end
        
        local onBoard_num_hundred = math.floor(unit:getDrawArgumentValue(442)*10)*100
        local onBoard_num_tens = math.floor(unit:getDrawArgumentValue(31)*10)*10
        local onBoard_num_ones = math.floor(unit:getDrawArgumentValue(32)*10)

        return onBoard_num_hundred + onBoard_num_tens + onBoard_num_ones
    end

    function BAirboss:turnIntoWind(Delay)
        --Get weather data.
        local CarrierPoint = self.carrierUnit:getPoint()
        local windBearing, windSpeed = Utils.getWindBearingAndVel(CarrierPoint) -- radians & m/s

        local magnetic = 7
        if env.mission.theatre == 'GermanyCW' then
            magnetic = 4
        end

        local carrierSpeed = 5/1.944 --m/s

        local carrierHeading = math.deg(windBearing)+180+9+magnetic
        if windSpeed < 1 then 
            carrierHeading = Utils.getObjHeading(self.carrierUnit)
            carrierHeading = math.deg(carrierHeading)
        end

        if carrierHeading > 360 then
            carrierHeading = carrierHeading - 360
        end

        --Calculate wind speed on runway in knots. If wind speed < 25 knots, try to make it 25 knots on deck.
        if windSpeed <= 20/1.944 then
            carrierSpeed = 20/1.944-windSpeed
        end

        -- Find available waypoint and path.
        -- Requirements: 6 hours long voyage time based on carrier speed.
        --               No shallow water or land between current location and final waypoint.
        local totalDistance = carrierSpeed * 6*60*60 --meters

        local newWaypoint_1 = {
            point = BAirboss.findClearPath(CarrierPoint,carrierHeading,totalDistance),
            speed = carrierSpeed
        }
        local newWaypoint_2 = {
            point = BAirboss.findClearPath(newWaypoint_1.point,carrierHeading+90,totalDistance/10),
            speed = 10/1.944
        }
        local newWaypoint_3 = {
            point = BAirboss.findClearPath(newWaypoint_2.point,carrierHeading+180,Utils.get2DDist(CarrierPoint,newWaypoint_1.point)),
            speed = 27/1.944
        }
        local newWaypoint_4 = {
            point = {x=CarrierPoint.x,y=CarrierPoint.z},
            speed = 10/1.944
        }

        local Mission = {
            id = 'Mission',
            params = { 
                route = { 
                    points = { 
                        [1] = { 
                            type = "Turning Point", 
                            action = "Turning Point", 
                            x = newWaypoint_1.point.x, 
                            y = newWaypoint_1.point.y, 
                            speed = newWaypoint_1.speed, 
                        }, 
                        [2] = { 
                            type = "Turning Point", 
                            action = "Turning Point", 
                            x = newWaypoint_2.point.x, 
                            y = newWaypoint_2.point.y, 
                            speed = newWaypoint_2.speed, 
                        }, 
                        [3] = { 
                            type = "Turning Point", 
                            action = "Turning Point", 
                            x = newWaypoint_3.point.x, 
                            y = newWaypoint_3.point.y, 
                            speed = newWaypoint_3.speed, 
                        }, 
                        [4] = { 
                            type = "Turning Point", 
                            action = "Turning Point", 
                            x = newWaypoint_4.point.x, 
                            y = newWaypoint_4.point.y, 
                            speed = newWaypoint_4.speed, 
                            task = {
                                id = 'ComboTask',
                                params = {
                                    [1] = {
                                        id = 'goToWaypoint', 
                                        params = { 
                                            fromWaypointIndex = 4,
                                            goToWaypointIndex = 1, 
                                        } 
                                    },
                                }
                            }
                        }, 
                    },
                },
            },
        }

        local Controller = self.carrierUnit:getGroup():getController()
        if not Controller then return self end

        Delay = Delay or 1
        timer.scheduleFunction(function (Mission)
            Controller:setTask(Mission)
        end,Mission,timer.getTime()+Delay)
        
        return self
    end

    function BAirboss:setMorse(MorseCode)
        self.Morse = MorseCode or BAirboss.DefaultSettings[self.carrierUnit:getTypeName()].Morse
        return self
    end

    function BAirboss:setATC(frequency,modulation,power)

        power = power or 10
        modulation = modulation or radio.modulation.AM

        frequency = RadioManager.registerChannel({
            userName = self.carrierUnit:getGroup():getName(),
            freq = frequency or BAirboss.DefaultSettings[self.carrierUnit:getTypeName()].ATC,
            modulation = modulation,
        })

        local SetFrequency = { 
            id = 'SetFrequencyForUnit', 
            params = { 
              frequency = frequency, 
              modulation = modulation, 
              power = power, 
              unitId = self.carrierUnit:getID(),
            } 
          }

        local Controller = self.carrierUnit:getController()
        if not Controller then
            return self
        end

        Controller:setCommand(SetFrequency)

        return self
    end

    function BAirboss:setTACAN(Channel,Mode,MorseCode)
        local Mode = Mode or BAirboss.DefaultSettings[self.carrierUnit:getTypeName()].TACAN_Mode

        Mode = string.upper(Mode)
        if Mode~='X' or Mode~='Y' then
            Mode = BAirboss.DefaultSettings[self.carrierUnit:getTypeName()].TACAN_Mode
        end
        self.TACAN_Mode = Mode or BAirboss.DefaultSettings[self.carrierUnit:getTypeName()].TACAN_Mode

        -- self:setMorse(MorseCode)

        self.TACAN = Channel or BAirboss.DefaultSettings[self.carrierUnit:getTypeName()].TACAN
        self.TACAN = TACANManager.registerChannel({
            userName =self.carrierUnit:getGroup():getName(),
            channel = self.TACAN,
            mode = self.TACAN_Mode,
            callSign = MorseCode,

        }) or BAirboss.DefaultSettings[self.carrierUnit:getTypeName()].TACAN

        local ActivateBeacon ={
            id = 'ActivateBeacon', 
            params = { 
              type = 4, 
              system = 3, 
              callsign = self.Morse,  
              frequency = Utils.TACANToFrequency(self.TACAN, self.TACAN_Mode),
              channel = Channel,
              bearing = true,
              modeChannel = Mode,
              unitId = self.carrierUnit:getID()
            } 
        }

        local Controller = self.carrierUnit:getController()
        if not Controller then
            return
        end

        Controller:setCommand(ActivateBeacon)
        self.TACAN_ON = true
        
        return self
    end
    
    function BAirboss:setICLS(Channel,MorseCode)
        self.ICLS = Channel or BAirboss.DefaultSettings[self.carrierUnit:getTypeName()].ICLS

        self:setMorse(MorseCode)

        local ActivateICLS= { 
            id = 'ActivateICLS', 
            params = {
              type = 131584,
              channel = self.ICLS, 
              unitId = self.carrierUnit:getID(), 
            }
        }

        local Controller = self.carrierUnit:getController()
        if not Controller then return end
        
        Controller:setCommand(ActivateICLS)
        self.ICLS_ON = true
        
        return self
    end

    function BAirboss:setLink4(Channel)
        self.LINK4 = Channel or BAirboss.DefaultSettings[self.carrierUnit:getTypeName()].LINK4

        if self.LINK4 < 1000000 then self.LINK4 = self.LINK4 * 1000000 end
        local ActivateLink4= { 
            id = 'ActivateLink4', 
            params = {
                unitId = self.carrierUnit:getID(),
                frequency = self.LINK4, 
            } 
        }

        local Controller = self.carrierUnit:getController()
        if not Controller then return end
        
        Controller:setCommand(ActivateLink4)
        self.LINK4_ON = true

        return self
    end

    -- @param #boolean
    -- @return #Self
    function BAirboss:setACLS(switch)
        switch = switch or true

        local ACLSTask = { 
            id = 'ActivateACLS', 
            params = {
                unitId = self.carrierUnit:getID(), 
            } 
        }

        if not switch then
            ACLSTask = { 
                id = 'DeactivateACLS', 
                params = {} 
            }
        end
        
        local Controller = self.carrierUnit:getController()
        if not Controller then return end
        
        Controller:setCommand(ACLSTask)
        self.ACLS_ON = switch

        return self
    end

    function BAirboss:InitBeacons()
        
            self:setMorse()
            self:setICLS()
            self:setLink4()
            self:setACLS(true)
            self:setATC()

        timer.scheduleFunction(function (obj)
            obj:setTACAN()
        end,self,timer.getTime()+2)

        return self
    end

--- Create Player Menu.
    local menuEvent = {}
    function menuEvent:onEvent(event)
        if event.id == world.event.S_EVENT_BIRTH then
            local unit = event.initiator
            if not unit then return end
            if not unit.getGroup then return end
            if not unit.isExist then return end
            if not unit:isExist() then return end
            if not unit.getLife then return end
            if unit:getLife() < 1 then return end
            if not unit.getTypeName then return end

            local typeName = unit:getTypeName()
            local canLandOnCarrier = false
            for _,typeString in pairs(BAirboss.AircraftTypes) do
                if typeName == typeString then
                    canLandOnCarrier = true
                    break
                end
            end
            if not canLandOnCarrier then return end

            local group = unit:getGroup()
            if not group or not group.isExist or not unit:isExist() then return end

            local hint = '====================\n'
            hint = hint..Localizations:translate('If the carrier TACAN not set as expected, please use: \"Reset beacons.\" in [F10 Radio] menu function.','cn')
            hint = hint..'\n===================='

            trigger.action.outTextForGroup(group:getID(),hint,30)
            BAirboss.createPlayerMenu(group)
        end
    end
    world.addEventHandler(menuEvent)

    function BAirboss.createPlayerMenu(playerGroup)
        if not playerGroup or not playerGroup.getID or not playerGroup.getName then return end

        local groupID = playerGroup:getID()
        if not groupID then return end

        local groupName = playerGroup:getName()
        if not groupName then return end

        BAirboss.menu = BAirboss.menu or {}
        if BAirboss.menu[groupName] then
            missionCommands.removeItemForGroup(groupID,BAirboss.menu[groupName])
            BAirboss.menu[groupName] = nil
        end

        local mainMenuName = Localizations:translate('Airboss','cn')
        local mainMenu = missionCommands.addSubMenuForGroup(groupID,mainMenuName)

        -- Set CASE
        if not BAirboss.case then
            local fogThickness = world.weather.getFogThickness()
            local fogVisibilityDistance =  world.weather.getFogVisibilityDistance()

            if fogThickness <= 1200 and fogVisibilityDistance <= 8000 then
                BAirboss.case = 1
            end

            if fogThickness > 1200 and fogVisibilityDistance > 8000 then
                BAirboss.case = 3
            end
        end

        local newCase = 3
        if BAirboss.case == 3 then newCase = 1 end

        local caseIIIMenuName = string.format(Localizations:translate('Start CASE %d training','cn'),newCase)
        missionCommands.addCommandForGroup(groupID,caseIIIMenuName,mainMenu,BAirboss.setCASE,{case = newCase, playerGroup = playerGroup})

        BAirboss.menu[groupName] = mainMenu

        --Manually init Beacons
        local manualInitBeaconsMenuName = Localizations:translate('Reset beacons.','cn')
        missionCommands.addCommandForGroup(groupID,manualInitBeaconsMenuName,mainMenu,BAirboss.restartBeacons,{playerGroup = playerGroup})


        return true
    end

-- Menu functions.
    function BAirboss.setCASE(vars)
        if not vars then return end
        if not vars.case then return end
        if not vars.playerGroup or not vars.playerGroup.getUnit then return end

        local playerUnit = vars.playerGroup:getUnit(1)
        if not playerUnit or not playerUnit.getPlayerName or not playerUnit.getLife or playerUnit:getLife() <1 then return end

        local playerName = playerUnit:getPlayerName()
        if not playerName then return end

        local case = tonumber(vars.case)
        if case < 1 or case > 3 then return end
        
        local delay_Start = 60
        local delay_End = 3600

        local fog = {
            {delay_Start,8000,1200},
        }

        local msg = string.format(Localizations:translate('Player [%s] starts a CASE III training, please call carrier after %dmins. Weather will last for %d hours.','cn'),playerName,delay_Start/60,delay_End/3600)

        if case == 1 then
            fog = {{delay_Start,0,0}}
            msg = string.format(Localizations:translate('Player [%s] starts a CASE I training, please call carrier after %dmins.','cn'),playerName,delay_Start/60)
        end
        
        trigger.action.outText(msg,delay_Start)
        world.weather.setFogAnimation(fog)
        if case == 3 then
            timer.scheduleFunction(function ()
                local fog = {{60,0,0},}
                world.weather.setFogAnimation(fog)
            end,nil,timer.getTime()+delay_End)
        end

        BAirboss.case = vars.case
        BAirboss.createPlayerMenu(vars.playerGroup)
    end

    function BAirboss.restartBeacons(vars)
        if vars and vars.playerGroup and vars.playerGroup.getUnit then 
            local playerUnit = vars.playerGroup:getUnit(1)
            if not playerUnit or not playerUnit.getPlayerName or not playerUnit.getLife or playerUnit:getLife() <1 then return end
        
            local playerName = playerUnit:getPlayerName()

            local msg = string.format(Localizations:translate('Player [%s] resets all carrier beacons.','cn'),playerName)
            trigger.action.outText(msg,10)
        end

        for name, carrier in pairs(BAirboss.AllCarriers) do
            carrier:InitBeacons()
        end
    end
-- Menu functions end.


end


local Airboss_Roosevelt = BAirboss:New('USS Theodore Roosevelt')
Airboss_Roosevelt:turnIntoWind()

local Airboss_Sten = BAirboss:New('John C. Stennis')
Airboss_Sten:turnIntoWind()

Utils.Debug("Airboss_Roosevelt: "..Airboss_Roosevelt.carrierUnit:getName())

Utils.Debug("Airboss_Sten: "..Airboss_Sten.carrierUnit:getName())

trigger.action.outText('Start BAirboss',10)